#include <netinet/ip.h>
#include <netinet/ether.h>
#include <netinet/tcp.h>
#include <netinet/in.h>

#include "dispatcher.h"
#include "Session.h"

using namespace std;

Dispatcher::Dispatcher()
	: analIndex_(-1)
{
}

Dispatcher::~Dispatcher() {
	map<tuple<uint32_t, uint32_t, uint16_t, uint16_t>, Session*>::iterator it;
	for (it=sessions_.begin(); it!=sessions_.end(); ++it)
		delete it->second;
}

void Dispatcher::addAnalizer(Analizer *a) {
	analizers_.push_back(a);
}

void Dispatcher::dispatch(const uint8_t *data, int length) {
	ip *ipPtr;
	tcphdr *tcpPtr;
	uint32_t ipLen, addrSrc, addrDst;
	uint16_t portSrc, portDst;
	bool closing = false;

	/* Aquire frame pointers */
	ipPtr = (ip*) (data + ETH_HLEN);
	ipLen = ipPtr->ip_hl * 4;
	tcpPtr = (tcphdr*) (data + ETH_HLEN + ipLen);

	/* Get port and ip numbers */
	addrSrc = ntohl(ipPtr->ip_src.s_addr);
	addrDst = ntohl(ipPtr->ip_dst.s_addr);
#ifdef __FAVOR_BSD
	portSrc = ntohs(tcpPtr->th_sport);
	portDst = ntohs(tcpPtr->th_dport);
#else
	portSrc = ntohs(tcpPtr->source);
	portDst = ntohs(tcpPtr->dest);
#endif

	/* Check if this is the last packet of TCP session. */
#ifdef __FAVOR_BSD
	closing = tcpPtr->th_flags & TH_FIN;
#else
	closing = tcpPtr->fin;
#endif

	/* Dispatch!!! */
	Analizer *analizer;
	map<tuple<uint32_t, uint32_t, uint16_t, uint16_t>, Session*>::iterator sIter;
	tuple<uint32_t, uint32_t, uint16_t,uint16_t> id(addrSrc, addrDst, portSrc, portDst);

	sIter = sessions_.find(id);
	/* If there is no session for incoming packet, treat it as a new 
	   connection. Create a new session, pick up an Analizer and tie it with 
	   the session. */
	if (sIter == sessions_.end()) {
		Session *s = new Session(addrSrc, addrDst, portSrc, portDst);
		sessions_[id] = s;
		analizer = getNextAnalizer();
		analTree_[s] = analizer;
		analizer->push(s, data + ETH_HLEN,
					length - ETH_HLEN, ipLen, closing);
		
		if (closing)
			removeSession(s);
	/* If we have information about the session, call appropriate analizer */
	} else {
		analizer = analTree_[sIter->second];
		analizer->push(sIter->second, data + ETH_HLEN,
					length - ETH_HLEN, ipLen, closing);
		
		if (closing)
			removeSession(sIter->second);
	}
}

Analizer *Dispatcher::getNextAnalizer() {
	analIndex_ = (analIndex_ + 1) % analizers_.size();
	return analizers_[analIndex_];
}

void Dispatcher::removeSession(Session *s) {
	map<tuple<uint32_t, uint32_t, uint16_t, uint16_t>, Session*>::iterator it1;
	for (it1 = sessions_.begin(); it1 != sessions_.end(); ++it1) {
		if (it1->second == s) {
			sessions_.erase( it1 );
			break;
		}
	}
	
	map<Session*, Analizer*>::iterator it2;
	it2 = analTree_.find( s );
	if (it2 != analTree_.end())
		analTree_.erase( it2 );
}



